/* Copyright (C) 2003, Charles Wang.
 * Author:  Charles Wang <charles@linux.net.cn>
 * All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANT; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public LIcense for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA.
 */
#include  <stdio.h>
#include  <stdlib.h>
#include  <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
  
#include  <newt.h>
#include  <pci/pci.h>
#include  "mblog.h"
#include  "newtutils.h"
#include  "utils.h"

/* /usr/share/hwdata/pcitable is no longer availabe in new hwdata rpm. */
/* #define   USE_PCITABLE */

/* The maximum number items which load_pcitable can be loaded. */
#define   MAXPD   1024
typedef struct {
#ifdef USE_PCITABLE
    int       vendid, devid;
    int       subvendid, subdevid;
#endif
    char      module[36];
    char      desc[76];
}  pcidevice;

extern const char * kernelver;

int qsort_pdcmp(const void *v0, const void *v1) {
    return  strcmp(((const pcidevice *)v0)->desc,
		   ((const pcidevice *)v1)->desc);
}
static int load_pcitable(pcidevice *pdarr, int sz_pdarr, const char *drvtype);

const char * loadscsidrv_runner(void) {
    newtGrid        grid, btngrid;
    newtComponent   form, listbox, load_btn, search_btn, back_btn;
    newtComponent   retco;
    int             numpd;
    pcidevice     * pdarr, *pcidev;
    const char    * nextstep;

    pdarr = malloc(sizeof(pcidevice) * MAXPD);
    if (pdarr == NULL) {
	newtWinMessage("Error", "OK", "Not enough memory!");
	return  NULL;
    }

    grid = newtCreateGrid(1, 2);

    listbox = newtListbox(-1, -1, 16,
			  NEWT_FLAG_BORDER | NEWT_FLAG_SCROLL | NEWT_FLAG_RETURNEXIT);
    newtListboxSetWidth(listbox, 74);
    newtGridSetField(grid, 0, 0, NEWT_GRID_COMPONENT,
		     listbox, 1, 1, 0, 0, 0, 0);

    btngrid = newtCreateGrid(3, 1);
    load_btn = newtButton(-1, -1,   "Load Driver");
    newtGridSetField(btngrid, 0, 0, NEWT_GRID_COMPONENT,
		     load_btn, 0, 0, 0, 0, 0, 0);
    search_btn = newtButton(-1, -1, " Re-Search ");
    newtGridSetField(btngrid, 1, 0, NEWT_GRID_COMPONENT,
		     search_btn, 0, 0, 0, 0, 0, 0);
    back_btn = newtButton(-1, -1,   "    Back   ");
    newtGridSetField(btngrid, 2, 0, NEWT_GRID_COMPONENT,
		     back_btn, 0, 0, 0, 0, 0, 0);

    newtGridSetField(grid, 0, 1, NEWT_GRID_SUBGRID,
		     btngrid, 1, 1, 1, 0, 0, 0);

    form = newtExtForm(NULL, NULL, 0);
    newtGridAddComponentsToForm(grid, form, 1);

    newtGridWrappedWindow(grid, "Load SCSI Driver");
    newtGridFree(grid, 1);

    numpd = load_pcitable(pdarr, MAXPD, "scsi");
    qsort(pdarr, numpd, sizeof(pdarr[0]), qsort_pdcmp);
    
    for (pcidev = pdarr; pcidev - pdarr < numpd; ++pcidev) {
        //printf("afterqsort: %s, %s, %d\n", pcidev->module, pcidev->desc, 
               //pcidev->module[strlen(pcidev->module)-1]);
	newtListboxAppendEntry(listbox, pcidev->module, pcidev);
        /*if (pcidev == pdarr || strcmp(pcidev->module, (pcidev-1)->module))
          newtListboxAppendEntry(listbox, pcidev->module, pcidev);*/
    }

    for (;;) {
	retco = newtExtRunForm(form);
	if (retco == search_btn) {  nextstep = "search";  break; }
	if (retco == back_btn) {  nextstep = NULL;  break; }
	/* Try to load the specified module. */
	if (retco == listbox || retco == load_btn) {
	    pcidev = newtListboxGetCurrent(listbox);
	    if (!pcidev) {
		newtWinMessage("Error", "OK",
			       "Please select a module to load.\n"
			       "If the required device has not been listed, load the module from the driver disk.\n");
		continue;
	    }
	    if (check_kmodule_loaded(pcidev->module)) {
		newtWinMessage("Infor", "OK",
			       "The module '%s' has been loaded already.", pcidev->module);
		continue;
	    }
	    if (do_modprobe("sd_mod") < 0) {
		//newtWinMessage("Error", "OK", "Load module 'sd_mod' failed.");
		//continue;
                ; // don't report error yet
	    }
            sleep(1);
	    if (do_modprobe(pcidev->module) >= 0)
		newtWinMessage("Prompt", "OK",
			       "The module '%s' has been loaded successfully.",
			       pcidev->module);
	    else
		newtWinMessage("Error", "OK",
			       "Load module '%s' failed.",
			       pcidev->module);
	}
    }

    newtFormDestroy(form);
    newtPopWindow();

    free(pdarr);
    return  nextstep;
}

#ifdef USE_PCITABLE
static char * get_hex(char *buf, unsigned *result);
static char * get_str(char *buf, char *result, int maxlen);
static int  load_pcitable(pcidevice *pdarr, int sz_pdarr, const char *drvtype) {
    FILE  *ptfp;
    char   linebuf[512], *cur;
    pcidevice  *pcidev;

    static struct pci_access *pacc=NULL;

    pacc = pci_alloc();
    if (!pacc) return 0;
    pci_init(pacc);
    ptfp = fopen("/usr/share/hwdata/pcitable", "r");
    if (ptfp == NULL)  return 0;

    pcidev = pdarr;
    while (pcidev - pdarr < sz_pdarr &&
	   fgets(linebuf, sizeof(linebuf), ptfp) != NULL) {
	if (linebuf[0] != '0')  continue;
	if (linebuf[1] != 'x')  continue;
        memset(pcidev, sizeof(*pcidev), 0);
	cur = get_hex(linebuf, &(pcidev->vendid));
	cur = get_hex(cur, &(pcidev->devid));
	cur = get_hex(cur, &(pcidev->subvendid));
	cur = get_hex(cur, &(pcidev->subdevid));
	cur = get_str(cur, pcidev->module, sizeof(pcidev->module));
	/*cur = get_str(cur, pcidev->desc, sizeof(pcidev->desc));*/
        pci_lookup_name(pacc, pcidev->desc, sizeof(pcidev->desc),
                        PCI_LOOKUP_VENDOR | PCI_LOOKUP_DEVICE | (pcidev->subvendid != -1 ? PCI_LOOKUP_SUBSYSTEM : 0),
                        pcidev->vendid, pcidev->devid,
                        pcidev->subvendid,
                        pcidev->subdevid);
        /* new pcitable does not include desc */
        /*strcpy(pcidev->desc, pcidev->module);*/
        /*
        mblog("DRV: %d, %d, %d, %d, mod: %s, desc: %s\n", pcidev->vendid, pcidev->devid, pcidev->subvendid, pcidev->subdevid,
              pcidev->module, pcidev->desc);
        */
	if (check_drv(pcidev->module, drvtype))
	    ++pcidev;
    }
    fclose(ptfp);
    pci_cleanup(pacc);
    return  pcidev - pdarr;
}

static char * get_hex(char *buf, unsigned *result) {
    char  ch;
    *result = -1;
    buf += strspn(buf, " \t");
    if (*buf != '0')  return buf;
    if (*(++buf) != 'x')  return buf;
    *result = 0;
    while (((ch = *(++buf)) >= '0' && ch <= '9') ||
	   (ch >= 'a' && ch <= 'f'))
	*result = *result * 16 +
	    ((ch >= '0' && ch <= '9') ? (ch - '0') : (ch - 'a' + 10));
    return  buf;
}
static char * get_str(char *buf, char *result, int maxlen) {
    char  ch;
    buf += strspn(buf, " \t");
    if (*buf != '"')  return buf;
    --maxlen;  /* Reserve a char for tail 0. */
    while ((ch = *(++buf)) != 0 && ch != '"')
	if (maxlen > 0) {
	    if (ch == '\\' && buf[1] != 0)
		*result++ = *(++buf);
	    else
		*result++ = ch;
	    --maxlen;
	}
    *result = 0;
    return  ch == '"' ? ++buf : buf;
}
#else  /* !USE_PCITABLE */
/* just list the module files */
static int  load_pcitable(pcidevice *pdarr, int sz_pdarr, const char *drvtype) {
    char  dirnamebuf[256];
    int pdnum = 0;

    int filter_module(const char *_dirname,
                      const char *_basename, 
                      struct stat *_stat)
    {
        char *p;

        if (pdnum >= sz_pdarr)  /* stop if out of space */
            return -1;
        if (S_ISREG(_stat->st_mode)) {
            p = strrchr(_basename, '.');
            if (p &&
                (!strcmp(p, ".ko") ||
                 !strcmp(p, ".o"))) {
                xstrncpy(pdarr->module, _basename,
                         sizeof(pdarr->module), p - _basename);
                xstrncpy(pdarr->desc, _basename, 
                         sizeof(pdarr->desc), p - _basename);
                pdarr++, pdnum++;                
            }
        }
        return 0;
    }
    
    snprintf(dirnamebuf, sizeof(dirnamebuf),
	     "/lib/modules/%s/kernel/drivers/%s/", kernelver, drvtype);
    walk_tree(dirnamebuf, filter_module);
    return pdnum;
}
#endif
